package mbt

import (
	"errors"
	"sort"
	"sync"
	"time"
)

var (
	ErrKeyNotFound = errors.New(" Key not found in cache")
	ErrKeyNotFoundOrLoadable = errors.New(" Key not found and could not be loaded into cache")
	cache = make(map[string]*Cache)
	mutex sync.RWMutex
)

type Item struct {
	sync.RWMutex
	key           interface{}
	value         interface{}
	lifeSpan      time.Duration           //该单个缓存项的存活时间
	createdOn     time.Time               //该单个缓存项的创建时间戳
	accessedOn    time.Time               //该单个缓存项的被访问时间戳
	accessCount   int64                   //该单个缓存项的被访问频率
	aboutToExpire []func(key interface{}) //回调方法在从缓存中删除项之前触发
}

func NewItem(key interface{},value interface{},lifeSpan time.Duration) *Item {
	t := time.Now()
	return &Item{
		key:           key,
		lifeSpan:      lifeSpan,
		createdOn:     t,
		accessedOn:    t,
		accessCount:   0,
		aboutToExpire: nil,
		value:         value,
	}
}

// KeepAlive marks an item to be kept for another expireDuration period.
func (item *Item) KeepAlive() {
	item.Lock()
	defer item.Unlock()
	item.accessedOn = time.Now()
	item.accessCount++
}

// LifeSpan returns this item's expiration duration.
func (item *Item) LifeSpan() time.Duration {
	// immutable
	return item.lifeSpan
}

// AccessedOn returns when this item was last accessed.
func (item *Item) AccessedOn() time.Time {
	item.RLock()
	defer item.RUnlock()
	return item.accessedOn
}

// CreatedOn returns when this item was added to the cache.
func (item *Item) CreatedOn() time.Time {
	// immutable
	return item.createdOn
}

// AccessCount returns how often this item has been accessed.
func (item *Item) AccessCount() int64 {
	item.RLock()
	defer item.RUnlock()
	return item.accessCount
}

// Key returns the key of this cached item.
func (item *Item) Key() interface{} {
	// immutable
	return item.key
}

// Data returns the value of this cached item.
func (item *Item) Value() interface{} {
	// immutable
	return item.value
}

// SetAboutToExpireCallback configures a callback, which will be called right
// before the item is about to be removed from the cache.
func (item *Item) SetAboutToExpireCallback(f func(interface{})) {
	if len(item.aboutToExpire) > 0 {
		item.RemoveAboutToExpireCallback()
	}
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = append(item.aboutToExpire, f)
}

// AddAboutToExpireCallback appends a new callback to the AboutToExpire queue
func (item *Item) AddAboutToExpireCallback(f func(interface{})) {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = append(item.aboutToExpire, f)
}

// RemoveAboutToExpireCallback empties the about to expire callback queue
func (item *Item) RemoveAboutToExpireCallback() {
	item.Lock()
	defer item.Unlock()
	item.aboutToExpire = nil
}


func NewCache(table string) *Cache {
	mutex.RLock()
	t, ok := cache[table]
	mutex.RUnlock()
	if !ok {
		mutex.Lock()
		t, ok = cache[table]
		if !ok {
			t = &Cache{
				name:  table,
				data: make(map[interface{}]*Item),
			}
			cache[table] = t
		}
		mutex.Unlock()
	}
	return t
}

type Cache struct {
	sync.RWMutex
	name              string
	data              map[interface{}]*Item
	cleanupTimer      *time.Timer                                      //计时器负责触发清除
	cleanupInterval   time.Duration                                    //当前计时器持续时间
	loadData          func(key interface{}, args ...interface{}) *Item //尝试加载不存在的密钥时触发的回调方法
	addedItem         []func(item *Item)                               //向缓存中添加新项目时触发的回调方法。
	aboutToDeleteItem []func(item *Item)                               //在从缓存中删除项目之前触发了回调方法。
}

// Count returns how many items are currently stored in the cache.
func (table *Cache) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.data)
}

// Foreach all items
func (table *Cache)Range(trans func(key interface{}, item *Item)) {
	table.RLock()
	defer table.RUnlock()

	for k, v := range table.data {
		trans(k, v)
	}
}

// SetDataLoader configures a data-loader callback, which will be called when
// trying to access a non-existing key. The key and 0...n additional arguments
// are passed to the callback function.
func (table *Cache) SetDataLoader(f func(interface{}, ...interface{}) *Item) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// SetAddedItemCallback configures a callback, which will be called every time
// a new item is added to the cache.
func (table *Cache) SetAddedItemCallback(f func(*Item)) {
	if len(table.addedItem) > 0 {
		table.RemoveAddedItemCallbacks()
	}
	table.Lock()
	defer table.Unlock()
	table.addedItem = append(table.addedItem, f)
}

//AddAddedItemCallback appends a new callback to the addedItem queue
func (table *Cache) AddAddedItemCallback(f func(*Item)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = append(table.addedItem, f)
}

// RemoveAddedItemCallbacks empties the added item callback queue
func (table *Cache) RemoveAddedItemCallbacks() {
	table.Lock()
	defer table.Unlock()
	table.addedItem = nil
}

// SetAboutToDeleteItemCallback configures a callback, which will be called
// every time an item is about to be removed from the cache.
func (table *Cache) SetAboutToDeleteItemCallback(f func(*Item)) {
	if len(table.aboutToDeleteItem) > 0 {
		table.RemoveAboutToDeleteItemCallback()
	}
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = append(table.aboutToDeleteItem, f)
}

// AddAboutToDeleteItemCallback appends a new callback to the AboutToDeleteItem queue
func (table *Cache) AddAboutToDeleteItemCallback(f func(*Item)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = append(table.aboutToDeleteItem, f)
}

// RemoveAboutToDeleteItemCallback empties the about to delete item callback queue
func (table *Cache) RemoveAboutToDeleteItemCallback() {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = nil
}

// Expiration check loop, triggered by a self-adjusting timer.
func (table *Cache) expirationCheck() {
	table.Lock()
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, item := range table.data {
		item.RLock()
		lifeSpan := item.lifeSpan
		accessedOn := item.accessedOn
		item.RUnlock()

		if lifeSpan == 0 {
			continue
		}
		if now.Sub(accessedOn) >= lifeSpan {
			table.deleteInternal(key)
		} else {
			if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {
				smallestDuration = lifeSpan - now.Sub(accessedOn)
			}
		}
	}
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

func (table *Cache) addInternal(item *Item) {
	table.data[item.key] = item
	expDur := table.cleanupInterval
	addedItem := table.addedItem
	table.Unlock()
	if addedItem != nil {
		for _, callback := range addedItem {
			callback(item)
		}
	}
	if item.lifeSpan > 0 && (expDur == 0 || item.lifeSpan < expDur) {
		table.expirationCheck()
	}
}

func (table *Cache) Store(key interface{},value interface{}, lifeSpan time.Duration) *Item {
	item := NewItem(key,value,lifeSpan)
	table.Lock()
	table.addInternal(item)
	return item
}

func (table *Cache) deleteInternal(key interface{}) (*Item, error) {
	r, ok := table.data[key]
	if !ok {
		return nil, ErrKeyNotFound
	}
	aboutToDeleteItem := table.aboutToDeleteItem
	table.Unlock()
	if aboutToDeleteItem != nil {
		for _, callback := range aboutToDeleteItem {
			callback(r)
		}
	}
	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		for _, callback := range r.aboutToExpire {
			callback(key)
		}
	}
	table.Lock()
	delete(table.data, key)
	return r, nil
}

// Delete an item from the cache.
func (table *Cache) Delete(key interface{}) (*Item, error) {
	table.Lock()
	defer table.Unlock()

	return table.deleteInternal(key)
}

func (table *Cache) Exist(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.data[key]

	return ok
}

func (table *Cache) NotFoundAdd(key interface{}, value interface{}, lifeSpan time.Duration) bool {
	table.Lock()
	if _, ok := table.data[key]; ok {
		table.Unlock()
		return false
	}
	item := NewItem(key,value,lifeSpan)
	table.addInternal(item)
	return true
}

// Value returns an item from the cache and marks it to be kept alive. You can
// pass additional arguments to your DataLoader callback function.
func (table *Cache) Load(key interface{}, args ...interface{}) (*Item, error) {
	table.RLock()
	r, ok := table.data[key]
	loadData := table.loadData
	table.RUnlock()
	if ok {
		r.KeepAlive()
		return r, nil
	}
	if loadData != nil {
		item := loadData(key, args...)
		if item != nil {
			table.Store(key,item.value,item.lifeSpan)
			return item, nil
		}
		return nil, ErrKeyNotFoundOrLoadable
	}
	return nil, ErrKeyNotFound
}

func (table *Cache) Flush() {
	table.Lock()
	defer table.Unlock()
	table.data = make(map[interface{}]*Item)
	table.cleanupInterval = 0
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
}

type CacheItemPair struct {
	Key         interface{}
	AccessCount int64
}

type CacheItemPairList []CacheItemPair
func (p CacheItemPairList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p CacheItemPairList) Len() int           { return len(p) }
func (p CacheItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount }

func (table *Cache) MostAccessed(count int64) []*Item {
	table.RLock()
	defer table.RUnlock()
	p := make(CacheItemPairList, len(table.data))
	i := 0
	for k, v := range table.data {
		p[i] = CacheItemPair{k, v.accessCount}
		i++
	}
	sort.Sort(p)
	var r []*Item
	c := int64(0)
	for _, v := range p {
		if c >= count {
			break
		}
		item, ok := table.data[v.Key]
		if ok {
			r = append(r, item)
		}
		c++
	}
	return r
}

